using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.ServiceModel;
using System.Text;
using Shared = Faust.Andrew.MTG.Player.Shared;

namespace Faust.Andrew.MTG.Player
{
    internal class PlayerState : Shared.Player
    {
        private Random _rand;

        public PlayerState(Random rand)
        {
            _rand = rand;
            _actions = new Queue<Faust.Andrew.MTG.Player.Shared.Action>();
            _deck = new List<Faust.Andrew.MTG.Player.Shared.GameCard>();
            _allCards = new Dictionary<int, Faust.Andrew.MTG.Player.Shared.GameCard>();
        }

        private Queue<Shared.Action> _actions;
        private Dictionary<int, Shared.GameCard> _allCards;

        private List<Shared.GameCard> _deck;
        public List<Shared.GameCard> Deck
        {
            get
            {
                return _deck;
            }
            set { _deck = value; }
        }

        public override int DeckCount
        {
            get
            {
                return _deck.Count;
            }
            set
            {
                base.DeckCount = value;
            }
        }

        public override int HandCount
        {
            get
            {
                return (from card in _allCards.Values
                            where card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.Hand
                            select card).Count();
            }
            set
            {
                base.HandCount = value;
            }
        }

        public override int GraveyardCount
        {
	          get 
	        { 
		         return (from card in _allCards.Values
                             where card.Location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.Graveyard
                             select card).Count();
	        }
	          set 
	        { 
		        base.GraveyardCount = value;
	        }
        }

        public Shared.Action NextAction
        {
            get
            {
                if (_actions.Count > 0)
                {
                    return _actions.Dequeue();
                }
                else
                {
                    return null;
                }
            }
        }

        public Queue<Shared.Action> AllActions
        {
            get
            {
                Queue<Shared.Action> tempActions = _actions;
                _actions = new Queue<Faust.Andrew.MTG.Player.Shared.Action>();
                return tempActions;
            }
        }

        public void EnqueueAction(Shared.Action action)
        {
            if (_actions == null)
            {
                _actions = new Queue<Faust.Andrew.MTG.Player.Shared.Action>();
            }

            _actions.Enqueue(action);
        }

        public void ShuffleDeck()
        {
            ServerHelper.Shuffle(_deck, _rand);            
        }

        public void AddCardToDeck(Shared.GameCard card)
        {
            AddCardToDeck(card, Shared.DeckPosition.Top, 0);
        }

        public void AddCardToDeck(Shared.GameCard card, Shared.DeckPosition deckPosition, int order)
        {
            if (_deck.Contains(card))
            {
                _deck.Remove(card);
            }

            if (deckPosition == Shared.DeckPosition.Top)
            {
                _deck.Insert(0, card);
            }
            else if (deckPosition == Shared.DeckPosition.Bottom)
            {
                _deck.Add(card);
            }
            else if (deckPosition == Shared.DeckPosition.FromBottom)
            {
                if (order < 0) { order = 0; }
                if (order > _deck.Count) { order = _deck.Count; }

                _deck.Insert(_deck.Count - order, card);
            }
            else if (deckPosition == Shared.DeckPosition.FromTop)
            {
                if (order < 0) { order = 0; }
                if (order > _deck.Count) { order = _deck.Count; }
                _deck.Insert(order, card);
            }
            else
            {
                _deck.Insert(_rand.Next(0, _deck.Count), card);
            }

            card.Location.Zone = Faust.Andrew.MTG.Player.Shared.Zone.Deck;
            card.Location.PlayerID = PlayerID;

            if (!_allCards.Keys.Contains(card.CardID))
            {
                _allCards.Add(card.CardID, card);
            }
        }

        public Shared.GameCard DrawCard()
        {
            if (_deck.Count > 0)
            {
                return DrawCard(_deck[0].CardID);
            }
            else
            {
                return null;
            }
        }

        public Shared.GameCard DrawCard(int cardId)
        {
            Shared.GameCard card = _deck.SingleOrDefault(tempCard => tempCard.CardID == cardId);

            if (card == null)
            {
                return null;
            }

            _deck.Remove(card);

            card.Location.Zone = Faust.Andrew.MTG.Player.Shared.Zone.Hand;
            card.Location.Order = 0;

            return card;
        }

        public List<Shared.GameCard> GetRandomCards(int count, Shared.Zone sourceZone)
        {
            List<Shared.GameCard> cards = new List<Faust.Andrew.MTG.Player.Shared.GameCard>();
            
            //Get list of all the cards controlled by the player that are in the requested
            //zone
            var c = from card in _allCards.Values
                            where card.Location.Zone == sourceZone
                            && card.ControllerID == PlayerID
                            select card;

            List<Shared.GameCard> zoneCards = new List<Faust.Andrew.MTG.Player.Shared.GameCard>(c);
            if (count >= zoneCards.Count)
            {
                //If we need at least as many cards as are in the zone, we can just return them all.
                return zoneCards;
            }
            else
            {
                //We actually need to retrieve a subset of them.
                for (int i = 0; i < count; i++)
                {
                    //We get a random card. As long as the card is in the output list
                    //we try for another.
                    Shared.GameCard tempCard = zoneCards[_rand.Next(0, zoneCards.Count)];

                    while (cards.Contains(tempCard))
                    {
                        tempCard = zoneCards[_rand.Next(0, zoneCards.Count)];
                    }   
                    
                    //At this point we have a random card that hasn't already been selected so
                    //we add it to the output list.
                    cards.Add(tempCard);
                }
            }

            return cards;
        }

        public List<Shared.GameCard> DrawCards(int count)
        {
            List<Shared.GameCard> cards = new List<Faust.Andrew.MTG.Player.Shared.GameCard>();

            for (int i = 0; i < count; i++)
            {
                Shared.GameCard card = DrawCard();
                if (card != null)
                {
                    cards.Add(card);
                }
            }

            return cards;
        }

        public Shared.Player UpdatePlayer(Shared.Player player, ref string updateText)
        {
            //The code for setting the update text only returns one change, even though this code
            //looks like it may change multiple things. This is a little misleading. This method should
            //only ever be called when one thing is changing, so this method of returning the value should
            //be safe.
            if (LifeCounter != player.LifeCounter)
            {
                if (player.LifeCounter > LifeCounter)
                {
                    updateText = String.Format("Increased His/Her Life Counter to {0}", player.LifeCounter);
                }
                else
                {
                    updateText = String.Format("Decreased His/Her Life Counter to {0}", player.LifeCounter);
                }
            }
            LifeCounter = player.LifeCounter;

            if (IsDeckUnlocked != player.IsDeckUnlocked)
            {
                if (player.IsDeckUnlocked)
                {
                    updateText = "Unlocked His/Her deck";
                }
                else
                {
                    updateText = "Locked His/Her deck";
                }
            }
            IsDeckUnlocked = player.IsDeckUnlocked;

            if (ServerHelper.AreCountersUpdated(Counters, player.Counters))
            {
                updateText = "Changed counters on Self";
            }
            Counters = player.Counters;

            return CopyClientPlayerState();
        }

        public Shared.GameCard UpdateCard(Shared.GameCard card, ref string updateText)
        {
            if (!_allCards.ContainsKey(card.CardID))
            {
                return null;
            }

            bool wasChanged = false;
            Shared.GameCard tempCard = _allCards[card.CardID];

            //The code for setting the update text only returns one change, even though this code
            //looks like it may change multiple things. This is a little misleading. This method should
            //only ever be called when one thing is changing, so this method of returning the value should
            //be safe.
            if (tempCard.ControllerID != card.ControllerID)
            {
                updateText = "Transferred Control of ";
                wasChanged = true;
            }
            tempCard.ControllerID = card.ControllerID;


            if (tempCard.IsFaceDown != card.IsFaceDown)
            {
                updateText = "Morphed ";
                wasChanged = true;
            }
            tempCard.IsFaceDown = card.IsFaceDown;

            if (tempCard.ModifierText != card.ModifierText)
            {
                updateText = String.Format("Changed Modifier Text to {0} for ", card.ModifierText);
                wasChanged = true;
            }
            tempCard.ModifierText = card.ModifierText;

            if (tempCard.State != card.State)
            {
                if (card.State == Faust.Andrew.MTG.Player.Shared.CardState.Tapped)
                {
                    updateText = "Tapped ";
                }
                else
                {
                    updateText = "Untapped ";
                }
                wasChanged = true;
            }
            tempCard.State = card.State;

            if (tempCard.ControllerID != card.ControllerID)
            {
                updateText = "Transferred Control of ";
                wasChanged = true;
            }

            if (ServerHelper.AreCountersUpdated(tempCard.Counters, card.Counters))
            {
                updateText = "Changed counters on ";
                wasChanged = true;
            }
            tempCard.Counters = card.Counters;

            updateText += ServerHelper.GetPublicCardName(tempCard, tempCard.Location.Zone);

            if (wasChanged == true)
            {
                return tempCard;
            }
            else
            {
                return null;
            }
        }                

        public void AddCard(Shared.GameCard gameCard)
        {
            if (_allCards.ContainsKey(gameCard.CardID)) { return; }
            _allCards.Add(gameCard.CardID, gameCard);
        }

        public void RemoveCard(Shared.GameCard card)
        {
            if (!_allCards.ContainsKey(card.CardID)) { return; }

            if (_deck.Contains(card))
            {
                _deck.Remove(card);
            }

            _allCards.Remove(card.CardID);
        }

        public Shared.GameCard MoveCard(int cardId, Shared.Location location)
        {
            if (!_allCards.ContainsKey(cardId)) { return null; }

            Shared.GameCard tempCard = _allCards[cardId];
            if (tempCard.ControllerID != PlayerID)
            {
                if (tempCard.Location.PlayerID != PlayerID
                    || tempCard.Location.Zone != Faust.Andrew.MTG.Player.Shared.Zone.InPlay
                    || location.Zone != Faust.Andrew.MTG.Player.Shared.Zone.InPlay)
                {
                    //If the player isn't the controller, then we do nothing UNLESS the card is in the player's zone 
                    //and the player isn't trying to move it to another zone
                    return null;
                }
            }

            if (_deck.Contains(tempCard) && location.Zone != Faust.Andrew.MTG.Player.Shared.Zone.Deck)
            {
                _deck.Remove(tempCard);
            }

            //It's being moved off the table. Whenever this happens the card's location must revert
            //back to the owner's area
            if (location.Zone != Faust.Andrew.MTG.Player.Shared.Zone.InPlay)
            {
                tempCard.Location.PlayerID = PlayerID;
            }

            if (location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.Graveyard 
                || location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.Hand 
                || location.Zone == Faust.Andrew.MTG.Player.Shared.Zone.Deck)
            {
                //If the card is being put in the graveyard, deck or hand we need to reset it
                //to it's base state.
                tempCard.Counters = new ObservableCollection<Faust.Andrew.MTG.Player.Shared.Counter>();
                tempCard.IsFaceDown = false;
                tempCard.ModifierText = String.Empty;
                tempCard.ControllerID = tempCard.OwnerID;
                tempCard.State = Faust.Andrew.MTG.Player.Shared.CardState.Normal;
            }

            tempCard.Location = location;

            return tempCard;
        }

        //Gets a copy of the player object suitable for transfer to the client
        public Shared.Player CopyClientPlayerState()
        {
            return new Faust.Andrew.MTG.Player.Shared.Player()
            {
                PlayerID = PlayerID,
                Counters = Counters,
                DeckCount = DeckCount,
                DeckName = DeckName,
                HandCount = HandCount,
                GraveyardCount = GraveyardCount,
                LifeCounter = LifeCounter,
                Name = Name,
                IsDeckUnlocked = IsDeckUnlocked,
                Order = Order
            };
        }
    }
}
